﻿using System;
using System.ComponentModel;
using System.Net;
using System.Net.Sockets;
using Oxage.Common;
using Oxage.Forms;

namespace Oxage.Midi
{
	public class UdpConnector : IConnector
	{
		#region Members
		private bool isOpened = false;
		private UdpForm panel;
		private UdpClient clientSender;
		private UdpClient clientListener;
		private BackgroundWorker worker;
		#endregion

		#region Constructor
		public UdpConnector()
		{
			panel = new UdpForm();
			this.Hostname = "remote-device-hostname";
			this.Port = 6434;
		}
		#endregion

		#region Properties
		/// <summary>
		/// Gets or sets the hostname of remote device.
		/// </summary>
		public string Hostname
		{
			get
			{
				return panel.Hostname;
			}
			set
			{
				panel.Hostname = value;
			}
		}

		public int Port
		{
			get
			{
				return panel.Port;
			}
			set
			{
				panel.Port = value;
			}
		}
		#endregion

		#region Methods
		public void Listen(IPAddress address, int port, bool async)
		{
			if (async)
			{
				worker = new BackgroundWorker();
				worker.DoWork += (sender, e) =>
				{
					Listen(address, port, false);
				};
				worker.RunWorkerAsync();
			}
			else
			{
				IPEndPoint sender;
				MidiMessage message;
				byte[] buffer;

				try
				{
					sender = new IPEndPoint(address, 0);
					clientListener = new UdpClient(port);
					this.isOpened = true;

					while (clientListener != null && this.isOpened)
					{
						buffer = clientListener.Receive(ref sender);

						try
						{
							message = new MidiMessage();
							message.Decode(buffer);
							OnReceived(message);
						}
						catch
						{
						}
					}
				}
				catch
				{
					this.isOpened = false;
				}
			}
		}

		public override string ToString()
		{
			return "UDP";
		}
		#endregion

		#region IConnector Members
		public bool IsOpened
		{
			get { return this.isOpened; }
		}

		public DataDirection Direction
		{
			get { return DataDirection.Both; }
		}

		public void Open()
		{
			if (string.IsNullOrEmpty(this.Hostname))
			{
				throw new ArgumentException("Hostname cannot be empty!");
			}
			if (this.Port <= 0)
			{
				throw new ArgumentException("Please, check the port number!");
			}
			if (clientSender != null)
			{
				Close();
			}
			clientSender = new UdpClient(0);
			clientSender.Connect(this.Hostname, this.Port);
			Listen(IPAddress.Any, this.Port, true);
			panel.IsLocked = true;
			this.isOpened = true;
		}

		public void Close()
		{
			try
			{
				if (clientSender != null)
				{
					//May throw exception if object is already disposed
					clientSender.Close();
				}

				if (clientListener != null)
				{
					//May throw exception if object is already disposed
					clientListener.Close();
				}
			}
			finally
			{
				panel.IsLocked = false;
				this.isOpened = false;
				clientSender = null;
				clientListener = null;
			}
		}

		public void Send(IMessage message)
		{
			try
			{
				byte[] buffer = message.Encode();
				clientSender.Send(buffer, buffer.Length);
			}
			catch
			{
			}
		}

		public event EventHandler<EventArgs<IMessage>> Received;
		protected virtual void OnReceived(IMessage message)
		{
			if (Received != null)
			{
				Received(this, new EventArgs<IMessage>(message));
			}
		}

		public IConfigPanel GetConfigPanel()
		{
			return panel;
		}
		#endregion
	}
}
